/*
 *  Copyright (C) 2010 Francesco Boffa (aka AlfaOmega08)
 * 
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 * 
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 * 
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package Engine;

public class CpuInterpreter {
	Memory mem;
	long Cycles;
	long Regs[] = new long[32];
	public long Pc = 0;
	long Hi = 0, Lo = 0;
	volatile long Branch, Branch2, BranchPc, Target;
	InterpreterInstruction[] BaseSet = new InterpreterInstruction[64];
	InterpreterInstruction[] SpecSet = new InterpreterInstruction[64];
	InterpreterInstruction[] BranchSet = new InterpreterInstruction[32];

	public CpuInterpreter(Executable e, Memory m) throws Exception {
		mem = m;
		Pc = e.Pc & 0xFFFFFFFF;
		Regs[28] = e.Gp;
		Regs[29] = e.Sp;

		e.Load(mem);

		SetupBaseInstructions();
		SetupSpecialInstructions();
	}

	public CpuInterpreter(long pc, long gp, long sp, Memory m) {
		mem = m;
		Pc = pc;
		Regs[28] = gp;
		Regs[29] = sp;

		SetupBaseInstructions();
		SetupSpecialInstructions();
	}

	public void Execute() throws Exception {
		while (true) {
			ExecuteInstruction();
		}
	}

	public void ExecuteBlock() throws Exception {
		Branch2 = 0;
		while (Branch2 == 0) {
			ExecuteInstruction();
		}
	}

	private void ExecuteInstruction() throws Exception {
		long instr = mem.Read(Pc, 4);
		Pc += 4;
		Cycles++;
		BaseSet[Op(instr)].Execute(instr);
	}

	public static int Op(long instr) {
		return (int) (instr >> 26) & 0x3F;
	}

	public static int Rs(long instr) {
		return (int) (instr >> 21) & 0x1F;
	}

	public static int Rt(long instr) {
		return (int) (instr >> 16) & 0x1F;
	}

	public static int Rd(long instr) {
		return (int) (instr >> 11) & 0x1F;
	}

	public static int Shamt(long instr) {
		return (int) (instr >> 6) & 0x1F;
	}

	public static int Target(long instr) {
		return (int) instr & 0x3FFFFFF;
	}

	public static int Immediate(long instr) {
		return (int) instr & 0xFFFF;
	}

	public static int Funct(long instr) {
		return (int) instr & 0x3F;
	}

	public long JumpTarget(long instr) {
		return Target(instr) * 4 + (Pc & 0xF0000000);
	}

	public long BranchTarget(long instr) {
		return (long) ((short) Immediate(instr) * 4 + Pc);
	}

	class UnknownInstructionC implements InterpreterInstruction {
		public void Execute(long instr) {
			System.out.println("Unknown instruction executed: " + Long.toHexString(instr & 0xFFFFFFFF) + " at " + Pc);
		}
	}
	UnknownInstructionC UnknownInstruction = new UnknownInstructionC();

	private void SetupBaseInstructions() {
		for (int i = 0; i < 64; i++) {
			BaseSet[i] = UnknownInstruction;
		}

		BaseSet[0] = new InterpreterInstruction() {
			/* Special subset (why special? nothing really special about this) */
			public void Execute(long instr) throws Exception {
				if (instr == 0) { // NOP
					return;
				}
				SpecSet[Funct(instr)].Execute(instr);
			}
		};

		BaseSet[1] = new InterpreterInstruction() {
			/* Branch condition subset (not all branches are in this subset) */
			public void Execute(long instr) throws Exception {
				System.out.println("Special branches not handled still");
				BranchSet[Funct(instr)].Execute(instr);
			}
		};

		BaseSet[2] = new InterpreterInstruction() {
			/* J: Unconditional Jump */
			public void Execute(long instr) {
				DoBranch(JumpTarget(instr));
			}
		};

		BaseSet[3] = new InterpreterInstruction() {
			/* JAL: Unconditional Jump & Link */
			public void Execute(long instr) {
				Regs[31] = (Pc + 4) & 0xFFFFFFFF;
				long T = JumpTarget(instr);
				DoBranch(T);
			}
		};

		BaseSet[4] = new InterpreterInstruction() {
			/* BEQ: Branch if Equal */
			public void Execute(long instr) {
				if (Regs[Rs(instr)] == Regs[Rt(instr)]) {
					DoBranch(BranchTarget(instr));
				}
			}
		};

		BaseSet[5] = new InterpreterInstruction() {
			/* BNE: Branch if Not Equal */
			public void Execute(long instr) {
				if (Regs[Rs(instr)] != Regs[Rt(instr)]) {
					DoBranch(BranchTarget(instr));
				} else {
					System.out.println("Loop done");
				}
			}
		};

		BaseSet[6] = new InterpreterInstruction() {
			/* BLEZ */
			public void Execute(long instr) {
				if (Regs[Rs(instr)] <= 0) {
					DoBranch(BranchTarget(instr));
				}
			}
		};

		BaseSet[7] = new InterpreterInstruction() {
			/* BGTZ */
			public void Execute(long instr) {
				if (Regs[Rs(instr)] > 0) {
					DoBranch(BranchTarget(instr));
				}
			}
		};

		BaseSet[8] = new InterpreterInstruction() {
			/* ADDI */
			public void Execute(long instr) {
				Wr(Rt(instr), Regs[Rs(instr)] + (short) Immediate(instr));
			}
		};

		BaseSet[9] = new InterpreterInstruction() {
			/* ADDIU */
			public void Execute(long instr) {
				Wr(Rt(instr), Regs[Rs(instr)] + (short) Immediate(instr));
			}
		};

		BaseSet[10] = new InterpreterInstruction() {
			/* SLTI */
			public void Execute(long instr) {
				Wr(Rd(instr), (int) Regs[Rs(instr)] < (short) Immediate(instr) ? 1 : 0);
			}
		};

		BaseSet[11] = new InterpreterInstruction() {
			/* SLTIU */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] < (short) Immediate(instr) ? 1 : 0);
			}
		};

		BaseSet[12] = new InterpreterInstruction() {
			/* ANDI */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] & Immediate(instr));
			}
		};

		BaseSet[13] = new InterpreterInstruction() {
			/* ORI */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] | Immediate(instr));
			}
		};

		BaseSet[14] = new InterpreterInstruction() {
			/* XORI */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] ^ Immediate(instr));
			}
		};

		BaseSet[15] = new InterpreterInstruction() {
			/* LUI: Load Upper Immediate */
			public void Execute(long instr) {
				Wr(Rt(instr), Immediate(instr) << 16);
			}
		};

		BaseSet[16] = new InterpreterInstruction() {
			/* COP0 */
			public void Execute(long instr) {
				System.out.println("Cop0 not supported: " + Pc);
				System.exit(-1);
			}
		};

		BaseSet[17] = new InterpreterInstruction() {
			/* COP1 */
			public void Execute(long instr) {
				System.out.println("Cop1 not supported: " + Pc);
				System.exit(-1);
			}
		};

		BaseSet[18] = new InterpreterInstruction() {
			/* COP2 */
			public void Execute(long instr) {
				System.out.println("Cop2 not supported: " + Pc);
				System.exit(-1);
			}
		};

		BaseSet[19] = new InterpreterInstruction() {
			/* COP3 */
			public void Execute(long instr) {
				System.out.println("Cop3 not supported: " + Pc);
			}
		};

		BaseSet[32] = new InterpreterInstruction() {
			/* LB */
			public void Execute(long instr) throws Exception {
				Wr(Rt(instr), mem.Read(Regs[Rs(instr)] + (short) Immediate(instr), 1));
			}
		};

		BaseSet[33] = new InterpreterInstruction() {
			/* LH */
			public void Execute(long instr) throws Exception {
				Wr(Rt(instr), mem.Read(Regs[Rs(instr)] + (short) Immediate(instr), 2));
			}
		};


		final long[] LWL_MASK = new long[] {0xffffff, 0xffff, 0xff, 0};
		final long[] LWL_SHIFT = new long[] {24, 16, 8, 0};
		BaseSet[34] = new InterpreterInstruction() {
			/* LWL: thanks to PCSX */
			public void Execute(long instr) throws Exception {
				long addr = Regs[Rs(instr)] + (short) Immediate(instr);
				int shift = (int) addr & 3;
				long val = mem.Read(addr & ~3, 4);

				Wr(Rt(instr), ((int) Regs[Rt(instr)] & LWL_MASK[shift]) | ((int) val << LWL_SHIFT[shift]) & 0xFFFFFFFF);
			}
		};

		BaseSet[35] = new InterpreterInstruction() {
			/* LW */
			public void Execute(long instr) throws Exception {
				Wr(Rt(instr), mem.Read(Regs[Rs(instr)] + (short) Immediate(instr), 4));
			}
		};

		BaseSet[36] = new InterpreterInstruction() {
			/* LBU */
			public void Execute(long instr) throws Exception {
				Wr(Rt(instr), mem.Read(Regs[Rs(instr)] + (short) Immediate(instr), 1) & 0xFF);
			}
		};

		BaseSet[37] = new InterpreterInstruction() {
			/* LHU */
			public void Execute(long instr) throws Exception {
				Wr(Rt(instr), mem.Read(Regs[Rs(instr)] + (short) Immediate(instr), 2) & 0xFFFF);
			}
		};

		final long[] LWR_MASK = new long[] {0, 0xff000000, 0xffff0000, 0xffffff00};
		final long[] LWR_SHIFT = new long[] {0, 8, 16, 24};
		BaseSet[38] = new InterpreterInstruction() {
			/* LWR */
			public void Execute(long instr) throws Exception {
				long addr = Regs[Rs(instr)] + (short) Immediate(instr);
				int shift = (int) addr & 3;
				long val = mem.Read(addr & ~3, 4);

				Wr(Rt(instr), ((int) Regs[Rt(instr)] & LWR_MASK[shift]) | ((int) val >> LWR_SHIFT[shift]));
			}
		};

		BaseSet[40] = new InterpreterInstruction() {
			/* SB */
			public void Execute(long instr) throws Exception {
				mem.Write(Regs[Rs(instr)] + (short) Immediate(instr), Regs[Rt(instr)] & 0xFF, 1);
			}
		};

		BaseSet[41] = new InterpreterInstruction() {
			/* SH */
			public void Execute(long instr) throws Exception {
				mem.Write(Regs[Rs(instr)] + (short) Immediate(instr), Regs[Rt(instr)] & 0xFFFF, 2);
			}
		};

		final long[] SWL_MASK = new long[] {0xffffff00, 0xffff0000, 0xff000000, 0};
		final long[] SWL_SHIFT = new long[] {24, 16, 8, 0};
		BaseSet[42] = new InterpreterInstruction() {
			/* SWL */
			public void Execute(long instr) throws Exception {
				long addr = Regs[Rs(instr)] + (short) Immediate(instr);
				int shift = (int) addr & 3;
				long val = mem.Read(addr & ~3, 4);

				mem.Write(addr & ~3, (Regs[Rt(instr)] >> SWL_SHIFT[shift]) | (val & SWL_MASK[shift]), 4);
			}
		};

		BaseSet[43] = new InterpreterInstruction() {
			/* SW */
			public void Execute(long instr) throws Exception {
				mem.Write((int) Regs[Rt(instr)], Regs[Rs(instr)] + (short) Immediate(instr), 4);
			}
		};

		final long[] SWR_MASK = new long[] {0, 0xff, 0xffff, 0xffffff};
		final long[] SWR_SHIFT = new long[] {0, 8, 16, 24};
		BaseSet[46] = new InterpreterInstruction() {
			/* SWR */
			public void Execute(long instr) throws Exception {
				long addr = Regs[Rs(instr)] + (short) Immediate(instr);
				int shift = (int) addr & 3;
				long val = mem.Read(addr & ~3, 4);

				mem.Write(addr & ~3, (Regs[Rt(instr)] << SWR_SHIFT[shift]) | (val & SWR_MASK[shift]), 4);
			}
		};

		BaseSet[48] = new InterpreterInstruction() {
			/* LCW0 */
			public void Execute(long instr) {
				System.out.println("LCW0 unimplemented");
				System.exit(-1);
			}
		};

		BaseSet[49] = new InterpreterInstruction() {
			/* LCW1 */
			public void Execute(long instr) {
				System.out.println("LCW1 unimplemented");
				System.exit(-1);
			}
		};

		BaseSet[50] = new InterpreterInstruction() {
			/* LCW2 */
			public void Execute(long instr) {
				System.out.println("LCW2 unimplemented");
				System.exit(-1);
			}
		};

		BaseSet[51] = new InterpreterInstruction() {
			/* LCW3 */
			public void Execute(long instr) {
				System.out.println("LCW3 unimplemented");
				System.exit(-1);
			}
		};

		BaseSet[56] = new InterpreterInstruction() {
			/* SCW0 */
			public void Execute(long instr) {
				System.out.println("SCW0 unimplemented");
				System.exit(-1);
			}
		};

		BaseSet[57] = new InterpreterInstruction() {
			/* SCW1 */
			public void Execute(long instr) {
				System.out.println("SCW1 unimplemented");
				System.exit(-1);
			}
		};

		BaseSet[58] = new InterpreterInstruction() {
			/* SCW2 */
			public void Execute(long instr) {
				System.out.println("SCW2 unimplemented");
				System.exit(-1);
			}
		};

		BaseSet[59] = new InterpreterInstruction() {
			/* SCW3 */
			public void Execute(long instr) {
				System.out.println("SCW3 unimplemented");
				System.exit(-1);
			}
		};
	}

	private void SetupSpecialInstructions() {
		for (int i = 0; i < 64; i++) {
			SpecSet[i] = UnknownInstruction;
		}

		SpecSet[0] = new InterpreterInstruction() {
			/* SLL */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rt(instr)] << Shamt(instr));
			}
		};

		SpecSet[2] = new InterpreterInstruction() {
			/* SRL */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rt(instr)] >> Shamt(instr));
			}
		};

		SpecSet[3] = new InterpreterInstruction() {
			/* SRA */
			public void Execute(long instr) {
				Wr(Rd(instr), (int) Regs[Rt(instr)] >> Shamt(instr));
			}
		};

		SpecSet[4] = new InterpreterInstruction() {
			/* SLLV */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rt(instr)] << (Regs[Rs(instr)] & 0x1F));
			}
		};

		SpecSet[6] = new InterpreterInstruction() {
			/* SRLV */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rt(instr)] >> (Regs[Rs(instr)] & 0x1F));
			}
		};

		SpecSet[7] = new InterpreterInstruction() {
			/* SRAV */
			public void Execute(long instr) {
				Wr(Rd(instr), (int) Regs[Rt(instr)] >> (Regs[Rs(instr)] & 0x1F));
			}
		};

		SpecSet[8] = new InterpreterInstruction() {
			/* JR */
			public void Execute(long instr) {
				/*
				 * Seems that jumps to BIOS system calls are through JR.
				 * So when JR is used we need to check wheter the target
				 * is a BIOS syscall
				 */
				DoBranch(Regs[Rs(instr)]);
			}
		};

		SpecSet[9] = new InterpreterInstruction() {
			/* JALR */
			public void Execute(long instr) {
				Wr(Rd(instr), Pc + 4);
				DoBranch(Regs[Rs(instr)]);
			}
		};

		SpecSet[12] = new InterpreterInstruction() {
			/* SYSCALL */
			public void Execute(long instr) {
				System.out.println("Syscall opcode not implemented");
			}
		};

		SpecSet[13] = new InterpreterInstruction() {
			/* BREAK */
			public void Execute(long instr) {
				System.out.println("Break opcode not implemented");
			}
		};

		SpecSet[16] = new InterpreterInstruction() {
			/* MFHI */
			public void Execute(long instr) {
				Wr(Rd(instr), Hi);
			}
		};

		SpecSet[17] = new InterpreterInstruction() {
			/* MTHI */
			public void Execute(long instr) {
				Hi = Regs[Rd(instr)];
			}
		};

		SpecSet[18] = new InterpreterInstruction() {
			/* MFLO */
			public void Execute(long instr) {
				Wr(Rd(instr), Lo);
			}
		};

		SpecSet[19] = new InterpreterInstruction() {
			/* MTLO */
			public void Execute(long instr) {
				Lo = Regs[Rd(instr)];
			}
		};

		SpecSet[24] = new InterpreterInstruction() {
			/* MULT */
			public void Execute(long instr) {
				long res = (int) Regs[Rs(instr)] * (int) Regs[Rt(instr)];
				Lo = res & 0xFFFFFFFF;
				Hi = res >> 32;
			}
		};

		SpecSet[25] = new InterpreterInstruction() {
			/* MULTU */
			public void Execute(long instr) {
				long res = Regs[Rs(instr)] * Regs[Rt(instr)];
				Lo = res & 0xFFFFFFFF;
				Hi = res >> 32;
			}
		};

		SpecSet[26] = new InterpreterInstruction() {
			/* DIV */
			public void Execute(long instr) {
				Lo = (int) Regs[Rs(instr)] / (int) Regs[Rt(instr)];
				Hi = (int) Regs[Rs(instr)] % (int) Regs[Rt(instr)];
			}
		};

		SpecSet[27] = new InterpreterInstruction() {
			/* DIVU */
			public void Execute(long instr) {
				Lo = Regs[Rs(instr)] / Regs[Rt(instr)];
				Hi = Regs[Rs(instr)] % Regs[Rt(instr)];
			}
		};

		SpecSet[32] = new InterpreterInstruction() {
			/* ADD */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] + Regs[Rt(instr)]);
			}
		};

		SpecSet[33] = new InterpreterInstruction() {
			/* ADDU */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] + Regs[Rt(instr)]);
			}
		};

		SpecSet[34] = new InterpreterInstruction() {
			/* SUB */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] - Regs[Rt(instr)]);
			}
		};

		SpecSet[35] = new InterpreterInstruction() {
			/* SUBU */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] - Regs[Rt(instr)]);
			}
		};

		SpecSet[36] = new InterpreterInstruction() {
			/* AND */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] & Regs[Rt(instr)]);
			}
		};

		SpecSet[37] = new InterpreterInstruction() {
			/* OR */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] | Regs[Rt(instr)]);
			}
		};

		SpecSet[38] = new InterpreterInstruction() {
			/* XOR */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] ^ Regs[Rt(instr)]);
			}
		};

		SpecSet[39] = new InterpreterInstruction() {
			/* NOR */
			public void Execute(long instr) {
				Wr(Rd(instr), ~(Regs[Rs(instr)] | Regs[Rt(instr)]));
			}
		};

		SpecSet[42] = new InterpreterInstruction() {
			/* SLT */
			public void Execute(long instr) {
				Wr(Rd(instr), (int) Regs[Rs(instr)] < (int) Regs[Rt(instr)] ? 1 : 0);
			}
		};

		SpecSet[43] = new InterpreterInstruction() {
			/* SLTU */
			public void Execute(long instr) {
				Wr(Rd(instr), Regs[Rs(instr)] < Regs[Rt(instr)] ? 1 : 0);
			}
		};
	}

	/* This code is explained on the pPS1e Wiki */
	private void DoBranch(long target) {
		/* Delay slot instruction */
		long code = 0;

		System.out.println("" + Long.toHexString(target));

		Branch = Branch2 = 1;
		BranchPc = target;

		try {
			code = mem.Read(Pc, 4);
		} catch (Exception e) {
			System.out.println("Can't get the delay slot instruction");
			System.exit(-1);
		}

		Pc += 4;
		Cycles++;

		/* Checking for load delay */
		int op = Op(code);
/*		switch (op) {
			case 0x10:	// COP0
				switch (Rs(code)) {
					case 0x00:	// MFC0
					case 0x02:	// CFC0
						psxDelayTest(Rt(code), BranchPc);
						return;
				}
				break;
			case 0x12:	// COP2
				if (Funct(code) == 0) {
					switch (Rs(code)) {
						case 0x00:	// MFC2
						case 0x02:	// CFC2
							psxDelayTest(Rt(code), BranchPc);
							return;
					}
				}
				break;
			case 0x32:	// LWC2
				psxDelayTest(Rt(code), BranchPc);
				return;
			default:
				if (op >= 0x20 && op <= 0x26) {	// LB/LH/LWL/LW/LBU/LHU/LWR
					psxDelayTest(Rt(code), BranchPc);
					return;
				}
				break;
		}
*/
		try {
			BaseSet[op].Execute(code);
		} catch (Exception e) {
			System.out.println("Execution of the delay slot failed. Things may be fucked up...");
		}
		Branch = 0;
		Pc = BranchPc;
	}

	private void Wr(int n, long v) {
		if (n == 0) {
			return;
		}
		Regs[n] = v;
	}
}
